<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="robots" content="noodp" />
        <title>Java I/O - L_B__</title><meta name="referrer" content="no-referrer">
<meta name="description" content="Java I/O"><meta property="og:title" content="Java I/O" />
<meta property="og:description" content="Java I/O" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://acking-you.github.io/posts/java-io/" /><meta property="og:image" content="https://acking-you.github.io/logo.png"/><meta property="article:section" content="posts" />
<meta property="article:published_time" content="2022-01-23T00:00:00+00:00" />
<meta property="article:modified_time" content="2022-01-23T00:00:00+00:00" />

<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="https://acking-you.github.io/logo.png"/>

<meta name="twitter:title" content="Java I/O"/>
<meta name="twitter:description" content="Java I/O"/>
<meta name="application-name" content="FeelIt">
<meta name="apple-mobile-web-app-title" content="FeelIt"><meta name="theme-color" content="#ffffff"><meta name="msapplication-TileColor" content="#da532c"><link rel="canonical" href="https://acking-you.github.io/posts/java-io/" /><link rel="prev" href="https://acking-you.github.io/posts/java%E5%8F%8D%E5%B0%84%E4%B8%8E%E6%B3%A8%E5%B0%84/" /><link rel="next" href="https://acking-you.github.io/posts/c&#43;&#43;%E4%B8%8Epython%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%AF%B9%E6%AF%94/" /><link rel="stylesheet" href="/css/page.min.css"><link rel="stylesheet" href="/css/home.min.css"><script type="application/ld+json">
    {
        "@context": "http://schema.org",
        "@type": "BlogPosting",
        "headline": "Java I/O",
        "inLanguage": "zh-CN",
        "mainEntityOfPage": {
            "@type": "WebPage",
            "@id": "https:\/\/acking-you.github.io\/posts\/java-io\/"
        },"genre": "posts","keywords": "Java I\/O","wordcount":  8562 ,
        "url": "https:\/\/acking-you.github.io\/posts\/java-io\/","datePublished": "2022-01-23T00:00:00+00:00","dateModified": "2022-01-23T00:00:00+00:00","publisher": {
            "@type": "Organization",
            "name": "作者"},"author": {
                "@type": "Person",
                "name": "作者"
            },"description": "Java I/O"
    }
    </script></head><body data-header-desktop="auto" data-header-mobile="auto"><script>(window.localStorage && localStorage.getItem('theme') ? localStorage.getItem('theme') === 'dark' : ('auto' === 'auto' ? window.matchMedia('(prefers-color-scheme: dark)').matches : 'auto' === 'dark')) && document.body.setAttribute('theme', 'dark');</script>

        <div id="mask"></div><div class="wrapper"><header class="desktop" id="header-desktop">
    <div class="header-wrapper">
        <div class="header-title">
            <a href="/" title="L_B__">L_B__</a>
        </div>
        <div class="menu">
            <div class="menu-inner"><a class="menu-item" href="/posts/"> 文章 </a><a class="menu-item" href="/tags/"> 标签 </a><a class="menu-item" href="/categories/"> 分类 </a><span class="menu-item delimiter"></span><span class="menu-item search" id="search-desktop">
                        <input type="text" placeholder="搜索文章标题或内容..." id="search-input-desktop">
                        <a href="#" class="search-button search-toggle" id="search-toggle-desktop" title="搜索">
                            <i class="fas fa-search fa-fw"></i>
                        </a>
                        <a href="#" class="search-button search-clear" id="search-clear-desktop" title="清空">
                            <i class="fas fa-times-circle fa-fw"></i>
                        </a>
                        <span class="search-button search-loading" id="search-loading-desktop">
                            <i class="fas fa-spinner fa-fw fa-spin"></i>
                        </span>
                    </span><a href="javascript:void(0);" class="menu-item theme-switch" title="切换主题">
                    <i class="fas fa-adjust fa-fw"></i>
                </a>
            </div>
        </div>
    </div>
</header><header class="mobile" id="header-mobile">
    <div class="header-container">
        <div class="header-wrapper">
            <div class="header-title">
                <a href="/" title="L_B__">L_B__</a>
            </div>
            <div class="menu-toggle" id="menu-toggle-mobile">
                <span></span><span></span><span></span>
            </div>
        </div>
        <div class="menu" id="menu-mobile"><div class="search-wrapper">
                    <div class="search mobile" id="search-mobile">
                        <input type="text" placeholder="搜索文章标题或内容..." id="search-input-mobile">
                        <a href="#" class="search-button search-toggle" id="search-toggle-mobile" title="搜索">
                            <i class="fas fa-search fa-fw"></i>
                        </a>
                        <a href="#" class="search-button search-clear" id="search-clear-mobile" title="清空">
                            <i class="fas fa-times-circle fa-fw"></i>
                        </a>
                        <span class="search-button search-loading" id="search-loading-mobile">
                            <i class="fas fa-spinner fa-fw fa-spin"></i>
                        </span>
                    </div>
                    <a href="#" class="search-cancel" id="search-cancel-mobile">
                        取消
                    </a>
                </div><a class="menu-item" href="/posts/" title="">文章</a><a class="menu-item" href="/tags/" title="">标签</a><a class="menu-item" href="/categories/" title="">分类</a><div class="menu-item"><a href="javascript:void(0);" class="theme-switch" title="切换主题">
                    <i class="fas fa-adjust fa-fw"></i>
                </a>
            </div></div>
    </div>
</header><div class="search-dropdown desktop">
    <div id="search-dropdown-desktop"></div>
</div>
<div class="search-dropdown mobile">
    <div id="search-dropdown-mobile"></div>
</div><main class="main">
                <div class="container"><div class="toc" id="toc-auto">
            <h2 class="toc-title">目录</h2>
            <div class="toc-content" id="toc-content-auto"></div>
        </div><article class="page single" data-toc="disable"><div class="featured-image"><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://img-blog.csdnimg.cn/img_convert/e706274b97f65b3aac9471d1da024918.png#pic_center"
        data-srcset="https://img-blog.csdnimg.cn/img_convert/e706274b97f65b3aac9471d1da024918.png#pic_center, https://img-blog.csdnimg.cn/img_convert/e706274b97f65b3aac9471d1da024918.png#pic_center 1.5x, https://img-blog.csdnimg.cn/img_convert/e706274b97f65b3aac9471d1da024918.png#pic_center 2x"
        data-sizes="auto"
        alt="https://img-blog.csdnimg.cn/img_convert/e706274b97f65b3aac9471d1da024918.png#pic_center"
        title="Java I/O" /></div><div class="single-card" data-image="true"><h2 class="single-title animated flipInX">Java I/O</h2><div class="post-meta">
                <div class="post-meta-line"><span class="post-author"><a href="/" title="Author" rel=" author" class="author"><i class="fas fa-user-circle fa-fw"></i>作者</a></span>&nbsp;<span class="post-category">出版于  <a href="/categories/javase%E7%AC%94%E8%AE%B0/"><i class="far fa-folder fa-fw"></i>JavaSE笔记</a></span></div>
                <div class="post-meta-line"><span><i class="far fa-calendar-alt fa-fw"></i>&nbsp;<time datetime="2022-01-23">2022-01-23</time></span>&nbsp;<span><i class="fas fa-pencil-alt fa-fw"></i>&nbsp;约 8562 字</span>&nbsp;
                    <span><i class="far fa-clock fa-fw"></i>&nbsp;预计阅读 18 分钟</span>&nbsp;</div>
            </div>
            
            <hr><div class="details toc" id="toc-static"  data-kept="">
                    <div class="details-summary toc-title">
                        <span>目录</span>
                        <span><i class="details-icon fas fa-angle-right"></i></span>
                    </div>
                    <div class="details-content toc-content" id="toc-content-static"><nav id="TableOfContents">
  <ul>
    <li><a href="#文件流">文件流</a>
      <ul>
        <li><a href="#文件字节流">文件字节流</a></li>
        <li><a href="#文件字符流">文件字符流</a></li>
        <li><a href="#file类">File类</a></li>
      </ul>
    </li>
    <li><a href="#缓冲流">缓冲流</a>
      <ul>
        <li><a href="#缓冲字节流">缓冲字节流</a></li>
        <li><a href="#缓冲字符流">缓冲字符流</a></li>
      </ul>
    </li>
    <li><a href="#转换流">转换流</a></li>
    <li><a href="#打印流">打印流</a></li>
    <li><a href="#数据流">数据流</a></li>
    <li><a href="#对象流">对象流</a></li>
    <li><a href="#java-io编程实战">Java I/O编程实战</a>
      <ul>
        <li><a href="#图书管理系统">图书管理系统</a></li>
      </ul>
    </li>
  </ul>
</nav></div>
                </div><div class="content" id="content"><h1 id="java-io">Java I/O</h1>
<p><a href="https://www.bilibili.com/video/BV1Gv411T7pi?p=78" target="_blank" rel="noopener noreffer">配套视频</a></p>
<p><strong>注意：<strong>这块会涉及到</strong>操作系统</strong>和<strong>计算机组成原理</strong>相关内容。</p>
<p>I/O简而言之，就是输入输出，那么为什么会有I/O呢？其实I/O无时无刻都在我们的身边，比如读取硬盘上的文件，网络文件传输，鼠标键盘输入，也可以是接受单片机发回的数据，而能够支持这些操作的设备就是I/O设备。</p>
<p>我们可以大致看一下整个计算机的总线结构：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg2020.cnblogs.com%2Fblog%2F1896043%2F202005%2F1896043-20200507143508957-1866569205.jpg&amp;refer=http%3A%2F%2Fimg2020.cnblogs.com&amp;app=2002&amp;size=f9999,10000&amp;q=a80&amp;n=0&amp;g=0n&amp;fmt=jpeg?sec=1637387700&amp;t=e6a5ade66f8e4af2ac64d12e6dd77dec"
        data-srcset="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg2020.cnblogs.com%2Fblog%2F1896043%2F202005%2F1896043-20200507143508957-1866569205.jpg&amp;refer=http%3A%2F%2Fimg2020.cnblogs.com&amp;app=2002&amp;size=f9999%2c10000&amp;q=a80&amp;n=0&amp;g=0n&amp;fmt=jpeg?sec=1637387700&amp;t=e6a5ade66f8e4af2ac64d12e6dd77dec, https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg2020.cnblogs.com%2Fblog%2F1896043%2F202005%2F1896043-20200507143508957-1866569205.jpg&amp;refer=http%3A%2F%2Fimg2020.cnblogs.com&amp;app=2002&amp;size=f9999%2c10000&amp;q=a80&amp;n=0&amp;g=0n&amp;fmt=jpeg?sec=1637387700&amp;t=e6a5ade66f8e4af2ac64d12e6dd77dec 1.5x, https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg2020.cnblogs.com%2Fblog%2F1896043%2F202005%2F1896043-20200507143508957-1866569205.jpg&amp;refer=http%3A%2F%2Fimg2020.cnblogs.com&amp;app=2002&amp;size=f9999%2c10000&amp;q=a80&amp;n=0&amp;g=0n&amp;fmt=jpeg?sec=1637387700&amp;t=e6a5ade66f8e4af2ac64d12e6dd77dec 2x"
        data-sizes="auto"
        alt="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg2020.cnblogs.com%2Fblog%2F1896043%2F202005%2F1896043-20200507143508957-1866569205.jpg&amp;refer=http%3A%2F%2Fimg2020.cnblogs.com&amp;app=2002&amp;size=f9999,10000&amp;q=a80&amp;n=0&amp;g=0n&amp;fmt=jpeg?sec=1637387700&amp;t=e6a5ade66f8e4af2ac64d12e6dd77dec"
        title="img" /></p>
<p>常见的I/O设备一般是鼠标、键盘这类通过USB进行传输的外设或者是通过Sata接口或是M.2连接的硬盘。一般情况下，这些设备是由CPU发出指令通过南桥芯片间接进行控制，而不是由CPU直接操作。</p>
<p>而我们在程序中，想要读取这些外部连接的I/O设备中的内容，就需要将数据传输到内存中。而需要实现这样的操作，单单凭借一个小的程序是无法做到的，而操作系统（如：Windows/Linux/MacOS）就是专门用于控制和管理计算机硬件和软件资源的软件，我们需要读取一个IO设备的内容时，可以向操作系统发出请求，由操作系统帮助我们来和底层的硬件交互以完成我们的读取/写入请求。从读取硬盘文件的角度来说，不同的操作系统有着不同的文件系统（也就是文件在硬盘中的存储排列方式，如Windows就是NTFS、MacOS就是APFS），硬盘只能存储一个个0和1这样的二进制数据，至于0和1如何排列，各自又代表什么意思，就是由操作系统的文件系统来决定的。从网络通信角度来说，网络信号通过网卡等设备翻译为二进制信号，再交给系统进行读取，最后再由操作系统来给到程序。</p>
<p>JDK提供了一套用于IO操作的框架，根据流的传输方向和读取单位，分为字节流InputStream和OutputStream以及字符流Reader和Writer，当然，这里的Stream并不是前面集合框架认识的Stream，这里的流指的是数据流，通过流，我们就可以一直从流中读取数据，直到读取到尽头，或是不断向其中写入数据，直到我们写入完成。而这类IO就是我们所说的BIO，</p>
<p>字节流一次读取一个字节，也就是一个<code>byte</code>的大小，而字符流顾名思义，就是一次读取一个字符，也就是一个<code>char</code>的大小（在读取纯文本文件的时候更加适合），有关这两种流，会在后面详细介绍，这个章节我们需要学习16个关键的流。</p>
<h2 id="文件流">文件流</h2>
<p>要学习和使用IO，首先就要从最易于理解的读取文件开始说起。</p>
<h3 id="文件字节流">文件字节流</h3>
<p>首先介绍一下FileInputStream，通过它来获取文件的输入流。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="n">FileInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;路径&#34;</span><span class="o">);</span>
        <span class="c1">//路径支持相对路径和绝对路径
</span><span class="c1"></span>    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">FileNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>相对路径是在当前运行的路径下寻找文件，而绝对路径，是从根目录开始寻找。路径分割符支持使用<code>/</code>或是<code>\\</code>，但是不能写为<code>\</code>因为它是转义字符！</p>
<p>在使用完成一个流之后，必须关闭这个流来完成对资源的释放，否则资源会被一直占用！</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">FileInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>    <span class="c1">//定义可以先放在try外部
</span><span class="c1"></span>    <span class="k">try</span> <span class="o">{</span>
        <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;路径&#34;</span><span class="o">);</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">FileNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
        <span class="k">try</span> <span class="o">{</span>    <span class="c1">//建议在finally中进行，因为这个是任何情况都必须要执行的！
</span><span class="c1"></span>            <span class="k">if</span><span class="o">(</span><span class="n">inputStream</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="n">inputStream</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>虽然这样的写法才是最保险的，但是显得过于繁琐了，尤其是finally中再次嵌套了一个try-catch块，因此在JDK1.7新增了try-with-resource语法，用于简化这样的写法（本质上还是和这样的操作一致，只是换了个写法）</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>

    <span class="c1">//注意，这种语法只支持实现了AutoCloseable接口的类！
</span><span class="c1"></span>    <span class="k">try</span><span class="o">(</span><span class="n">FileInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;路径&#34;</span><span class="o">))</span> <span class="o">{</span>   <span class="c1">//直接在try()中定义要在完成之后释放的资源
</span><span class="c1"></span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>   <span class="c1">//这里变成IOException是因为调用close()可能会出现，而FileNotFoundException是继承自IOException的
</span><span class="c1"></span>        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
    <span class="c1">//无需再编写finally语句块，因为在最后自动帮我们调用了close()
</span><span class="c1"></span><span class="o">}</span>
</code></pre></div><p>之后为了方便，我们都使用此语法进行教学。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">//test.txt：a
</span><span class="c1"></span>    <span class="k">try</span><span class="o">(</span><span class="n">FileInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))</span> <span class="o">{</span>
        <span class="c1">//使用read()方法进行字符读取
</span><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">inputStream</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>  <span class="c1">//读取一个字节的数据（英文字母只占1字节，中文占2字节）
</span><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">inputStream</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>   <span class="c1">//唯一一个字节的内容已经读完了，再次读取返回-1表示没有内容了
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>使用read可以直接读取一个字节的数据，注意，流的内容是有限的，读取一个少一个！我们如果想一次性全部读取的话，可以直接使用一个while循环来完成：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">//test.txt：abcd
</span><span class="c1"></span>    <span class="k">try</span><span class="o">(</span><span class="n">FileInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))</span> <span class="o">{</span>
        <span class="kt">int</span> <span class="n">tmp</span><span class="o">;</span>
        <span class="k">while</span> <span class="o">((</span><span class="n">tmp</span> <span class="o">=</span> <span class="n">inputStream</span><span class="o">.</span><span class="na">read</span><span class="o">())</span> <span class="o">!=</span> <span class="o">-</span><span class="n">1</span><span class="o">){</span>   <span class="c1">//通过while循环来一次性读完内容
</span><span class="c1"></span>            <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span><span class="n">tmp</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>使用方法能查看当前可读的剩余字节数量（注意：并不一定真实的数据量就是这么多，尤其是在网络I/O操作时，这个方法只能进行一个预估也可以说是暂时能一次性读取的数量）</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="k">try</span><span class="o">(</span><span class="n">FileInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))</span> <span class="o">{</span>
    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">inputStream</span><span class="o">.</span><span class="na">available</span><span class="o">());</span>  <span class="c1">//查看剩余数量
</span><span class="c1"></span><span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
    <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div><p>当然，一个一个读取效率太低了，那能否一次性全部读取呢？我们可以预置一个合适容量的byte[]数组来存放。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">//test.txt：abcd
</span><span class="c1"></span>    <span class="k">try</span><span class="o">(</span><span class="n">FileInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))</span> <span class="o">{</span>
        <span class="kt">byte</span><span class="o">[]</span> <span class="n">bytes</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="n">inputStream</span><span class="o">.</span><span class="na">available</span><span class="o">()];</span>   <span class="c1">//我们可以提前准备好合适容量的byte数组来存放
</span><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">inputStream</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">bytes</span><span class="o">));</span>   <span class="c1">//一次性读取全部内容（返回值是读取的字节数）
</span><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="k">new</span> <span class="n">String</span><span class="o">(</span><span class="n">bytes</span><span class="o">));</span>   <span class="c1">//通过String(byte[])构造方法得到字符串
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>也可以控制要读取数量：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">inputStream</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">bytes</span><span class="o">,</span> <span class="n">1</span><span class="o">,</span> <span class="n">2</span><span class="o">));</span>   <span class="c1">//第二个参数是从给定数组的哪个位置开始放入内容，第三个参数是读取流中的字节数
</span></code></pre></div><p><strong>注意</strong>：一次性读取同单个读取一样，当没有任何数据可读时，依然会返回-1</p>
<p>通过<code>skip()</code>方法可以跳过指定数量的字节：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">//test.txt：abcd
</span><span class="c1"></span>    <span class="k">try</span><span class="o">(</span><span class="n">FileInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))</span> <span class="o">{</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">inputStream</span><span class="o">.</span><span class="na">skip</span><span class="o">(</span><span class="n">1</span><span class="o">));</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">inputStream</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>   <span class="c1">//跳过了一个字节
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>注意：FileInputStream是不支持<code>reset()</code>的，虽然有这个方法，但是这里先不提及。</p>
<p>既然有输入流，那么文件输出流也是必不可少的：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">//输出流也需要在最后调用close()方法，并且同样支持try-with-resource
</span><span class="c1"></span>    <span class="k">try</span><span class="o">(</span><span class="n">FileOutputStream</span> <span class="n">outputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileOutputStream</span><span class="o">(</span><span class="s">&#34;output.txt&#34;</span><span class="o">))</span> <span class="o">{</span>
        <span class="c1">//注意：若此文件不存在，会直接创建这个文件！
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>输出流没有<code>read()</code>操作而是<code>write()</code>操作，使用方法同输入流一样，只不过现在的方向变为我们向文件里写入内容：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span><span class="o">(</span><span class="n">FileOutputStream</span> <span class="n">outputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileOutputStream</span><span class="o">(</span><span class="s">&#34;output.txt&#34;</span><span class="o">))</span> <span class="o">{</span>
        <span class="n">outputStream</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="sc">&#39;c&#39;</span><span class="o">);</span>   <span class="c1">//同read一样，可以直接写入内容
</span><span class="c1"></span>      	<span class="n">outputStream</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="s">&#34;lbwnb&#34;</span><span class="o">.</span><span class="na">getBytes</span><span class="o">());</span>   <span class="c1">//也可以直接写入byte[]
</span><span class="c1"></span>      	<span class="n">outputStream</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="s">&#34;lbwnb&#34;</span><span class="o">.</span><span class="na">getBytes</span><span class="o">(),</span> <span class="n">0</span><span class="o">,</span> <span class="n">1</span><span class="o">);</span>  <span class="c1">//同上输入流
</span><span class="c1"></span>      	<span class="n">outputStream</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>  <span class="c1">//建议在最后执行一次刷新操作（强制写入）来保证数据正确写入到硬盘文件中
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>那么如果是我只想在文件尾部进行追加写入数据呢？我们可以调用另一个构造方法来实现：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span><span class="o">(</span><span class="n">FileOutputStream</span> <span class="n">outputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileOutputStream</span><span class="o">(</span><span class="s">&#34;output.txt&#34;</span><span class="o">,</span> <span class="kc">true</span><span class="o">))</span> <span class="o">{</span>
        <span class="n">outputStream</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="s">&#34;lb&#34;</span><span class="o">.</span><span class="na">getBytes</span><span class="o">());</span>   <span class="c1">//现在只会进行追加写入，而不是直接替换原文件内容
</span><span class="c1"></span>        <span class="n">outputStream</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>
    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>利用输入流和输出流，就可以轻松实现文件的拷贝了：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span><span class="o">(</span><span class="n">FileOutputStream</span> <span class="n">outputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileOutputStream</span><span class="o">(</span><span class="s">&#34;output.txt&#34;</span><span class="o">);</span>
        <span class="n">FileInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))</span> <span class="o">{</span>   <span class="c1">//可以写入多个
</span><span class="c1"></span>        <span class="kt">byte</span><span class="o">[]</span> <span class="n">bytes</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="n">10</span><span class="o">];</span>    <span class="c1">//使用长度为10的byte[]做传输媒介
</span><span class="c1"></span>        <span class="kt">int</span> <span class="n">tmp</span><span class="o">;</span>   <span class="c1">//存储本地读取字节数
</span><span class="c1"></span>        <span class="k">while</span> <span class="o">((</span><span class="n">tmp</span> <span class="o">=</span> <span class="n">inputStream</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">bytes</span><span class="o">))</span> <span class="o">!=</span> <span class="o">-</span><span class="n">1</span><span class="o">){</span>   <span class="c1">//直到读取完成为止
</span><span class="c1"></span>            <span class="n">outputStream</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">bytes</span><span class="o">,</span> <span class="n">0</span><span class="o">,</span> <span class="n">tmp</span><span class="o">);</span>    <span class="c1">//写入对应长度的数据到输出流
</span><span class="c1"></span>        <span class="o">}</span>
    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><h3 id="文件字符流">文件字符流</h3>
<p>字符流不同于字节，字符流是以一个具体的字符进行读取，因此它只适合读纯文本的文件，如果是其他类型的文件不适用：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span><span class="o">(</span><span class="n">FileReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileReader</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">)){</span>
      	<span class="n">reader</span><span class="o">.</span><span class="na">skip</span><span class="o">(</span><span class="n">1</span><span class="o">);</span>   <span class="c1">//现在跳过的是一个字符
</span><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">reader</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>   <span class="c1">//现在是按字符进行读取，而不是字节，因此可以直接读取到中文字符
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>同理，字符流只支持<code>char[]</code>类型作为存储：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span><span class="o">(</span><span class="n">FileReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileReader</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">)){</span>
        <span class="kt">char</span><span class="o">[]</span> <span class="n">str</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">char</span><span class="o">[</span><span class="n">10</span><span class="o">];</span>
        <span class="n">reader</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">str</span><span class="o">);</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str</span><span class="o">);</span>   <span class="c1">//直接读取到char[]中
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>既然有了Reader肯定也有Writer：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span><span class="o">(</span><span class="n">FileWriter</span> <span class="n">writer</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileWriter</span><span class="o">(</span><span class="s">&#34;output.txt&#34;</span><span class="o">)){</span>
      	<span class="n">writer</span><span class="o">.</span><span class="na">getEncoding</span><span class="o">();</span>   <span class="c1">//支持获取编码（不同的文本文件可能会有不同的编码类型）
</span><span class="c1"></span>       <span class="n">writer</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="sc">&#39;牛&#39;</span><span class="o">);</span>
       <span class="n">writer</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="sc">&#39;牛&#39;</span><span class="o">);</span>   <span class="c1">//其实功能和write一样
</span><span class="c1"></span>      	<span class="n">writer</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>   <span class="c1">//刷新
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>我们发现不仅有<code>write()</code>方法，还有一个<code>append()</code>方法，但是实际上他们效果是一样的，看源码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="cm">/**
</span><span class="cm"> * Appends the specified character to this writer.
</span><span class="cm"> *
</span><span class="cm"> * &lt;p&gt; An invocation of this method of the form &lt;tt&gt;out.append(c)&lt;/tt&gt;
</span><span class="cm"> * behaves in exactly the same way as the invocation
</span><span class="cm"> *
</span><span class="cm"> * &lt;pre&gt;
</span><span class="cm"> *     out.write(c) &lt;/pre&gt;
</span><span class="cm"> *
</span><span class="cm"> * @param  c
</span><span class="cm"> *         The 16-bit character to append
</span><span class="cm"> *
</span><span class="cm"> * @return  This writer
</span><span class="cm"> *
</span><span class="cm"> * @throws  IOException
</span><span class="cm"> *          If an I/O error occurs
</span><span class="cm"> *
</span><span class="cm"> * @since 1.5
</span><span class="cm"> */</span>
<span class="kd">public</span> <span class="n">Writer</span> <span class="nf">append</span><span class="o">(</span><span class="kt">char</span> <span class="n">c</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="o">{</span>
    <span class="n">write</span><span class="o">(</span><span class="n">c</span><span class="o">);</span>
    <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div><p>append支持像StringBuilder那样的链式调用，返回的是Writer对象本身。</p>
<p><strong>练习</strong>：尝试一下用Reader和Writer来拷贝纯文本文件</p>
<h3 id="file类">File类</h3>
<p>File类专门用于表示一个文件或文件夹，只不过它只是代表这个文件，但并不是这个文件本身。通过File对象，可以更好地管理和操作硬盘上的文件。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">File</span> <span class="n">file</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">);</span>   <span class="c1">//直接创建文件对象，可以是相对路径，也可以是绝对路径
</span><span class="c1"></span>    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">exists</span><span class="o">());</span>   <span class="c1">//此文件是否存在
</span><span class="c1"></span>    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">length</span><span class="o">());</span>   <span class="c1">//获取文件的大小
</span><span class="c1"></span>    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">isDirectory</span><span class="o">());</span>   <span class="c1">//是否为一个文件夹
</span><span class="c1"></span>    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">canRead</span><span class="o">());</span>   <span class="c1">//是否可读
</span><span class="c1"></span>    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">canWrite</span><span class="o">());</span>   <span class="c1">//是否可写
</span><span class="c1"></span>    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">canExecute</span><span class="o">());</span>   <span class="c1">//是否可执行
</span><span class="c1"></span><span class="o">}</span>
</code></pre></div><p>通过File对象，我们就能快速得到文件的所有信息，如果是文件夹，还可以获取文件夹内部的文件列表等内容：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="n">File</span> <span class="n">file</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="s">&#34;/&#34;</span><span class="o">);</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">Arrays</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">list</span><span class="o">()));</span>   <span class="c1">//快速获取文件夹下的文件名称列表
</span><span class="c1"></span><span class="k">for</span> <span class="o">(</span><span class="n">File</span> <span class="n">f</span> <span class="o">:</span> <span class="n">file</span><span class="o">.</span><span class="na">listFiles</span><span class="o">()){</span>   <span class="c1">//所有子文件的File对象
</span><span class="c1"></span>    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">f</span><span class="o">.</span><span class="na">getAbsolutePath</span><span class="o">());</span>   <span class="c1">//获取文件的绝对路径
</span><span class="c1"></span><span class="o">}</span>
</code></pre></div><p>如果我们希望读取某个文件的内容，可以直接将File作为参数传入字节流或是字符流：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="n">File</span> <span class="n">file</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">);</span>
<span class="k">try</span> <span class="o">(</span><span class="n">FileInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="n">file</span><span class="o">)){</span>   <span class="c1">//直接做参数
</span><span class="c1"></span>    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">inputStream</span><span class="o">.</span><span class="na">available</span><span class="o">());</span>
<span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
    <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div><p><strong>练习</strong>：尝试拷贝文件夹下的所有文件到另一个文件夹</p>
<hr>
<h2 id="缓冲流">缓冲流</h2>
<p>虽然普通的文件流读取文件数据非常便捷，但是每次都需要从外部I/O设备去获取数据，由于外部I/O设备的速度一般都达不到内存的读取速度，很有可能造成程序反应迟钝，因此性能还不够高，而缓冲流正如其名称一样，它能够提供一个缓冲，提前将部分内容存入内存（缓冲区）在下次读取时，如果缓冲区中存在此数据，则无需再去请求外部设备。同理，当向外部设备写入数据时，也是由缓冲区处理，而不是直接向外部设备写入。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.wityx.com%2Fimage%2F201908%2F480873DBD936EBA9518F721ACDC22BFE.png&amp;refer=http%3A%2F%2Fwww.wityx.com&amp;app=2002&amp;size=f9999,10000&amp;q=a80&amp;n=0&amp;g=0n&amp;fmt=jpeg?sec=1637457276&amp;t=b4f7d52f08d9d5815baca0b21a01f925"
        data-srcset="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.wityx.com%2Fimage%2F201908%2F480873DBD936EBA9518F721ACDC22BFE.png&amp;refer=http%3A%2F%2Fwww.wityx.com&amp;app=2002&amp;size=f9999%2c10000&amp;q=a80&amp;n=0&amp;g=0n&amp;fmt=jpeg?sec=1637457276&amp;t=b4f7d52f08d9d5815baca0b21a01f925, https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.wityx.com%2Fimage%2F201908%2F480873DBD936EBA9518F721ACDC22BFE.png&amp;refer=http%3A%2F%2Fwww.wityx.com&amp;app=2002&amp;size=f9999%2c10000&amp;q=a80&amp;n=0&amp;g=0n&amp;fmt=jpeg?sec=1637457276&amp;t=b4f7d52f08d9d5815baca0b21a01f925 1.5x, https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.wityx.com%2Fimage%2F201908%2F480873DBD936EBA9518F721ACDC22BFE.png&amp;refer=http%3A%2F%2Fwww.wityx.com&amp;app=2002&amp;size=f9999%2c10000&amp;q=a80&amp;n=0&amp;g=0n&amp;fmt=jpeg?sec=1637457276&amp;t=b4f7d52f08d9d5815baca0b21a01f925 2x"
        data-sizes="auto"
        alt="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.wityx.com%2Fimage%2F201908%2F480873DBD936EBA9518F721ACDC22BFE.png&amp;refer=http%3A%2F%2Fwww.wityx.com&amp;app=2002&amp;size=f9999,10000&amp;q=a80&amp;n=0&amp;g=0n&amp;fmt=jpeg?sec=1637457276&amp;t=b4f7d52f08d9d5815baca0b21a01f925"
        title="img" /></p>
<h3 id="缓冲字节流">缓冲字节流</h3>
<p>要创建一个缓冲字节流，只需要将原本的流作为构造参数传入BufferedInputStream即可：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">BufferedInputStream</span> <span class="n">bufferedInputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BufferedInputStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))){</span>   <span class="c1">//传入FileInputStream
</span><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>   <span class="c1">//操作和原来的流是一样的
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>实际上进行I/O操作的并不是BufferedInputStream，而是我们传入的FileInputStream，而BufferedInputStream虽然有着同样的方法，但是进行了一些额外的处理然后再调用FileInputStream的同名方法，这样的写法称为<code>装饰者模式</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kt">void</span> <span class="nf">close</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="o">{</span>
    <span class="kt">byte</span><span class="o">[]</span> <span class="n">buffer</span><span class="o">;</span>
    <span class="k">while</span> <span class="o">(</span> <span class="o">(</span><span class="n">buffer</span> <span class="o">=</span> <span class="n">buf</span><span class="o">)</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">bufUpdater</span><span class="o">.</span><span class="na">compareAndSet</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">buffer</span><span class="o">,</span> <span class="kc">null</span><span class="o">))</span> <span class="o">{</span>  <span class="c1">//CAS无锁算法，并发会用到，暂时不管
</span><span class="c1"></span>            <span class="n">InputStream</span> <span class="n">input</span> <span class="o">=</span> <span class="n">in</span><span class="o">;</span>
            <span class="n">in</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">input</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span>
                <span class="n">input</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="c1">// Else retry in case a new buf was CASed in fill()
</span><span class="c1"></span>    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>实际上这种模式是父类FilterInputStream提供的规范，后面我们还会讲到更多FilterInputStream的子类。</p>
<p>我们可以发现在BufferedInputStream中还存在一个专门用于缓存的数组：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="cm">/**
</span><span class="cm"> * The internal buffer array where the data is stored. When necessary,
</span><span class="cm"> * it may be replaced by another array of
</span><span class="cm"> * a different size.
</span><span class="cm"> */</span>
<span class="kd">protected</span> <span class="kd">volatile</span> <span class="kt">byte</span> <span class="n">buf</span><span class="o">[];</span>
</code></pre></div><p>I/O操作一般不能重复读取内容（比如键盘发送的信号，主机接收了就没了），而缓冲流提供了缓冲机制，一部分内容可以被暂时保存，BufferedInputStream支持<code>reset()</code>和<code>mark()</code>操作，首先我们来看看<code>mark()</code>方法的介绍：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="cm">/**
</span><span class="cm"> * Marks the current position in this input stream. A subsequent
</span><span class="cm"> * call to the &lt;code&gt;reset&lt;/code&gt; method repositions this stream at
</span><span class="cm"> * the last marked position so that subsequent reads re-read the same bytes.
</span><span class="cm"> * &lt;p&gt;
</span><span class="cm"> * The &lt;code&gt;readlimit&lt;/code&gt; argument tells this input stream to
</span><span class="cm"> * allow that many bytes to be read before the mark position gets
</span><span class="cm"> * invalidated.
</span><span class="cm"> * &lt;p&gt;
</span><span class="cm"> * This method simply performs &lt;code&gt;in.mark(readlimit)&lt;/code&gt;.
</span><span class="cm"> *
</span><span class="cm"> * @param   readlimit   the maximum limit of bytes that can be read before
</span><span class="cm"> *                      the mark position becomes invalid.
</span><span class="cm"> * @see     java.io.FilterInputStream#in
</span><span class="cm"> * @see     java.io.FilterInputStream#reset()
</span><span class="cm"> */</span>
<span class="kd">public</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">mark</span><span class="o">(</span><span class="kt">int</span> <span class="n">readlimit</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">in</span><span class="o">.</span><span class="na">mark</span><span class="o">(</span><span class="n">readlimit</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div><p>当调用<code>mark()</code>之后，输入流会以某种方式保留之后读取的<code>readlimit</code>数量的内容，当读取的内容数量超过<code>readlimit</code>则之后的内容不会被保留，当调用<code>reset()</code>之后，会使得当前的读取位置回到<code>mark()</code>调用时的位置。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">BufferedInputStream</span> <span class="n">bufferedInputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BufferedInputStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))){</span>
        <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">mark</span><span class="o">(</span><span class="n">1</span><span class="o">);</span>   <span class="c1">//只保留之后的1个字符
</span><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>
        <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">reset</span><span class="o">();</span>   <span class="c1">//回到mark时的位置
</span><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>
    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>我们发现虽然后面的部分没有保存，但是依然能够正常读取，其实<code>mark()</code>后保存的读取内容是取<code>readlimit</code>和BufferedInputStream类的缓冲区大小两者中的最大值，而并非完全由<code>readlimit</code>确定。因此我们限制一下缓冲区大小，再来观察一下结果：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">BufferedInputStream</span> <span class="n">bufferedInputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BufferedInputStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">),</span> <span class="n">1</span><span class="o">)){</span>  <span class="c1">//将缓冲区大小设置为1
</span><span class="c1"></span>        <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">mark</span><span class="o">(</span><span class="n">1</span><span class="o">);</span>   <span class="c1">//只保留之后的1个字符
</span><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>   <span class="c1">//已经超过了readlimit，继续读取会导致mark失效
</span><span class="c1"></span>        <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">reset</span><span class="o">();</span>   <span class="c1">//mark已经失效，无法reset()
</span><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">bufferedInputStream</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>
    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>了解完了BufferedInputStream之后，我们再来看看BufferedOutputStream，其实和BufferedInputStream原理差不多，只是反向操作：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">BufferedOutputStream</span> <span class="n">outputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BufferedOutputStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileOutputStream</span><span class="o">(</span><span class="s">&#34;output.txt&#34;</span><span class="o">))){</span>
        <span class="n">outputStream</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="s">&#34;lbwnb&#34;</span><span class="o">.</span><span class="na">getBytes</span><span class="o">());</span>
        <span class="n">outputStream</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>
    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>操作和FileOutputStream一致，这里就不多做介绍了。</p>
<h3 id="缓冲字符流">缓冲字符流</h3>
<p>缓存字符流和缓冲字节流一样，也有一个专门的缓冲区，BufferedReader构造时需要传入一个Reader对象：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">BufferedReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="n">FileReader</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))){</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">reader</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>
    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>使用和reader也是一样的，内部也包含一个缓存数组：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kt">char</span> <span class="n">cb</span><span class="o">[];</span>
</code></pre></div><p>相比Reader更方便的是，它支持按行读取：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">BufferedReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="n">FileReader</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))){</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">reader</span><span class="o">.</span><span class="na">readLine</span><span class="o">());</span>   <span class="c1">//按行读取
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>读取后直接得到一个字符串，当然，它还能把每一行内容依次转换为集合类提到的Stream流：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">BufferedReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="n">FileReader</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))){</span>
        <span class="n">reader</span>
                <span class="o">.</span><span class="na">lines</span><span class="o">()</span>
                <span class="o">.</span><span class="na">limit</span><span class="o">(</span><span class="n">2</span><span class="o">)</span>
                <span class="o">.</span><span class="na">distinct</span><span class="o">()</span>
                <span class="o">.</span><span class="na">sorted</span><span class="o">()</span>
                <span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">);</span>
    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>它同样也支持<code>mark()</code>和<code>reset()</code>操作：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">BufferedReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="n">FileReader</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))){</span>
        <span class="n">reader</span><span class="o">.</span><span class="na">mark</span><span class="o">(</span><span class="n">1</span><span class="o">);</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">reader</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>
        <span class="n">reader</span><span class="o">.</span><span class="na">reset</span><span class="o">();</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">reader</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>
    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>BufferedReader处理纯文本文件时就更加方便了，BufferedWriter在处理时也同样方便：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">BufferedWriter</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BufferedWriter</span><span class="o">(</span><span class="k">new</span> <span class="n">FileWriter</span><span class="o">(</span><span class="s">&#34;output.txt&#34;</span><span class="o">))){</span>
        <span class="n">reader</span><span class="o">.</span><span class="na">newLine</span><span class="o">();</span>   <span class="c1">//使用newLine进行换行
</span><span class="c1"></span>        <span class="n">reader</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="s">&#34;汉堡做滴彳亍不彳亍&#34;</span><span class="o">);</span>   <span class="c1">//可以直接写入一个字符串
</span><span class="c1"></span>      	<span class="n">reader</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>   <span class="c1">//清空缓冲区
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><hr>
<h2 id="转换流">转换流</h2>
<p>有时会遇到这样一个很麻烦的问题：我这里读取的是一个字符串或是一个个字符，但是我只能往一个OutputStream里输出，但是OutputStream又只支持byte类型，如果要往里面写入内容，进行数据转换就会很麻烦，那么能否有更加简便的方式来做这样的事情呢？</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span><span class="o">(</span><span class="n">OutputStreamWriter</span> <span class="n">writer</span> <span class="o">=</span> <span class="k">new</span> <span class="n">OutputStreamWriter</span><span class="o">(</span><span class="k">new</span> <span class="n">FileOutputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))){</span>  <span class="c1">//虽然给定的是FileOutputStream，但是现在支持以Writer的方式进行写入
</span><span class="c1"></span>        <span class="n">writer</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="s">&#34;lbwnb&#34;</span><span class="o">);</span>   <span class="c1">//以操作Writer的样子写入OutputStream
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>同样的，我们现在只拿到了一个InputStream，但是我们希望能够按字符的方式读取，我们就可以使用InputStreamReader来帮助我们实现：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span><span class="o">(</span><span class="n">InputStreamReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="n">InputStreamReader</span><span class="o">(</span><span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))){</span>  <span class="c1">//虽然给定的是FileInputStream，但是现在支持以Reader的方式进行读取
</span><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span> <span class="n">reader</span><span class="o">.</span><span class="na">read</span><span class="o">());</span>
    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>InputStreamReader和OutputStreamWriter本质也是Reader和Writer，因此可以直接放入BufferedReader来实现更加方便的操作。</p>
<hr>
<h2 id="打印流">打印流</h2>
<p>打印流其实我们从一开始就在使用了，比如<code>System.out</code>就是一个PrintStream，PrintStream也继承自FilterOutputStream类因此依然是装饰我们传入的输出流，但是它存在自动刷新机制，例如当向PrintStream流中写入一个字节数组后自动调用<code>flush()</code>方法。PrintStream也永远不会抛出异常，而是使用内部检查机制<code>checkError()</code>方法进行错误检查。最方便的是，它能够格式化任意的类型，将它们以字符串的形式写入到输出流。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">final</span> <span class="kd">static</span> <span class="n">PrintStream</span> <span class="n">out</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
</code></pre></div><p>可以看到<code>System.out</code>也是PrintStream，不过默认是向控制台打印，我们也可以让它向文件中打印：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span><span class="o">(</span><span class="n">PrintStream</span> <span class="n">stream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PrintStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileOutputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))){</span>
        <span class="n">stream</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;lbwnb&#34;</span><span class="o">);</span>   <span class="c1">//其实System.out就是一个PrintStream
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>我们平时使用的<code>println</code>方法就是PrintStream中的方法，它会直接打印基本数据类型或是调用对象的<code>toString()</code>方法得到一个字符串，并将字符串转换为字符，放入缓冲区再经过转换流输出到给定的输出流上。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://img-blog.csdn.net/20180906143936647?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpbGkxMzg5Nzc0MTU1NA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70"
        data-srcset="https://img-blog.csdn.net/20180906143936647?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpbGkxMzg5Nzc0MTU1NA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70, https://img-blog.csdn.net/20180906143936647?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpbGkxMzg5Nzc0MTU1NA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70 1.5x, https://img-blog.csdn.net/20180906143936647?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpbGkxMzg5Nzc0MTU1NA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70 2x"
        data-sizes="auto"
        alt="https://img-blog.csdn.net/20180906143936647?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpbGkxMzg5Nzc0MTU1NA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70"
        title="img" /></p>
<p>因此实际上内部还包含这两个内容：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="cm">/**
</span><span class="cm"> * Track both the text- and character-output streams, so that their buffers
</span><span class="cm"> * can be flushed without flushing the entire stream.
</span><span class="cm"> */</span>
<span class="kd">private</span> <span class="n">BufferedWriter</span> <span class="n">textOut</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">OutputStreamWriter</span> <span class="n">charOut</span><span class="o">;</span>
</code></pre></div><p>与此相同的还有一个PrintWriter，不过他们的功能基本一致，PrintWriter的构造方法可以接受一个Writer作为参数，这里就不再做过多阐述了。</p>
<hr>
<h2 id="数据流">数据流</h2>
<p>数据流DataInputStream也是FilterInputStream的子类，同样采用装饰者模式，最大的不同是它支持基本数据类型的直接读取：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">DataInputStream</span> <span class="n">dataInputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">DataInputStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;test.txt&#34;</span><span class="o">))){</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">dataInputStream</span><span class="o">.</span><span class="na">readBoolean</span><span class="o">());</span>   <span class="c1">//直接将数据读取为任意基本数据类型
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>用于写入基本数据类型：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">DataOutputStream</span> <span class="n">dataOutputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">DataOutputStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileOutputStream</span><span class="o">(</span><span class="s">&#34;output.txt&#34;</span><span class="o">))){</span>
        <span class="n">dataOutputStream</span><span class="o">.</span><span class="na">writeBoolean</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>注意，写入的是二进制数据，并不是写入的字符串，使用DataInputStream可以读取，一般他们是配合一起使用的。</p>
<h2 id="对象流">对象流</h2>
<p>既然基本数据类型能够读取和写入基本数据类型，那么能否将对象也支持呢？ObjectOutputStream不仅支持基本数据类型，通过对对象的序列化操作，以某种格式保存对象，来支持对象类型的IO，注意：它不是继承自FilterInputStream的。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">ObjectOutputStream</span> <span class="n">outputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ObjectOutputStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileOutputStream</span><span class="o">(</span><span class="s">&#34;output.txt&#34;</span><span class="o">));</span>
         <span class="n">ObjectInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ObjectInputStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;output.txt&#34;</span><span class="o">))){</span>
        <span class="n">People</span> <span class="n">people</span> <span class="o">=</span> <span class="k">new</span> <span class="n">People</span><span class="o">(</span><span class="s">&#34;lbw&#34;</span><span class="o">);</span>
        <span class="n">outputStream</span><span class="o">.</span><span class="na">writeObject</span><span class="o">(</span><span class="n">people</span><span class="o">);</span>
      	<span class="n">outputStream</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>
        <span class="n">people</span> <span class="o">=</span> <span class="o">(</span><span class="n">People</span><span class="o">)</span> <span class="n">inputStream</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">people</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="o">|</span> <span class="n">ClassNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="kd">static</span> <span class="kd">class</span> <span class="nc">People</span> <span class="kd">implements</span> <span class="n">Serializable</span><span class="o">{</span>   <span class="c1">//必须实现Serializable接口才能被序列化
</span><span class="c1"></span>    <span class="n">String</span> <span class="n">name</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">People</span><span class="o">(</span><span class="n">String</span> <span class="n">name</span><span class="o">){</span>
        <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>在我们后续的操作中，有可能会使得这个类的一些结构发生变化，而原来保存的数据只适用于之前版本的这个类，因此我们需要一种方法来区分类的不同版本：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">static</span> <span class="kd">class</span> <span class="nc">People</span> <span class="kd">implements</span> <span class="n">Serializable</span><span class="o">{</span>
    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">serialVersionUID</span> <span class="o">=</span> <span class="n">123456</span><span class="o">;</span>   <span class="c1">//在序列化时，会被自动添加这个属性，它代表当前类的版本，我们也可以手动指定版本。
</span><span class="c1"></span>
    <span class="n">String</span> <span class="n">name</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">People</span><span class="o">(</span><span class="n">String</span> <span class="n">name</span><span class="o">){</span>
        <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>当发生版本不匹配时，会无法反序列化为对象：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="n">java</span><span class="o">.</span><span class="na">io</span><span class="o">.</span><span class="na">InvalidClassException</span><span class="o">:</span> <span class="n">com</span><span class="o">.</span><span class="na">test</span><span class="o">.</span><span class="na">Main$People</span><span class="o">;</span> <span class="n">local</span> <span class="kd">class</span> <span class="nc">incompatible</span><span class="o">:</span> <span class="n">stream</span> <span class="n">classdesc</span> <span class="n">serialVersionUID</span> <span class="o">=</span> <span class="n">123456</span><span class="o">,</span> <span class="n">local</span> <span class="kd">class</span> <span class="nc">serialVersionUID</span> <span class="o">=</span> <span class="n">1234567</span>
	<span class="n">at</span> <span class="n">java</span><span class="o">.</span><span class="na">io</span><span class="o">.</span><span class="na">ObjectStreamClass</span><span class="o">.</span><span class="na">initNonProxy</span><span class="o">(</span><span class="n">ObjectStreamClass</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="n">699</span><span class="o">)</span>
	<span class="n">at</span> <span class="n">java</span><span class="o">.</span><span class="na">io</span><span class="o">.</span><span class="na">ObjectInputStream</span><span class="o">.</span><span class="na">readNonProxyDesc</span><span class="o">(</span><span class="n">ObjectInputStream</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="n">2003</span><span class="o">)</span>
	<span class="n">at</span> <span class="n">java</span><span class="o">.</span><span class="na">io</span><span class="o">.</span><span class="na">ObjectInputStream</span><span class="o">.</span><span class="na">readClassDesc</span><span class="o">(</span><span class="n">ObjectInputStream</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="n">1850</span><span class="o">)</span>
	<span class="n">at</span> <span class="n">java</span><span class="o">.</span><span class="na">io</span><span class="o">.</span><span class="na">ObjectInputStream</span><span class="o">.</span><span class="na">readOrdinaryObject</span><span class="o">(</span><span class="n">ObjectInputStream</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="n">2160</span><span class="o">)</span>
	<span class="n">at</span> <span class="n">java</span><span class="o">.</span><span class="na">io</span><span class="o">.</span><span class="na">ObjectInputStream</span><span class="o">.</span><span class="na">readObject0</span><span class="o">(</span><span class="n">ObjectInputStream</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="n">1667</span><span class="o">)</span>
	<span class="n">at</span> <span class="n">java</span><span class="o">.</span><span class="na">io</span><span class="o">.</span><span class="na">ObjectInputStream</span><span class="o">.</span><span class="na">readObject</span><span class="o">(</span><span class="n">ObjectInputStream</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="n">503</span><span class="o">)</span>
	<span class="n">at</span> <span class="n">java</span><span class="o">.</span><span class="na">io</span><span class="o">.</span><span class="na">ObjectInputStream</span><span class="o">.</span><span class="na">readObject</span><span class="o">(</span><span class="n">ObjectInputStream</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="n">461</span><span class="o">)</span>
	<span class="n">at</span> <span class="n">com</span><span class="o">.</span><span class="na">test</span><span class="o">.</span><span class="na">Main</span><span class="o">.</span><span class="na">main</span><span class="o">(</span><span class="n">Main</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="n">27</span><span class="o">)</span>
</code></pre></div><p>如果我们不希望某些属性参与到序列化中进行保存，我们可以添加<code>transient</code>关键字：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">(</span><span class="n">ObjectOutputStream</span> <span class="n">outputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ObjectOutputStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileOutputStream</span><span class="o">(</span><span class="s">&#34;output.txt&#34;</span><span class="o">));</span>
         <span class="n">ObjectInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ObjectInputStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;output.txt&#34;</span><span class="o">))){</span>
        <span class="n">People</span> <span class="n">people</span> <span class="o">=</span> <span class="k">new</span> <span class="n">People</span><span class="o">(</span><span class="s">&#34;lbw&#34;</span><span class="o">);</span>
        <span class="n">outputStream</span><span class="o">.</span><span class="na">writeObject</span><span class="o">(</span><span class="n">people</span><span class="o">);</span>
        <span class="n">outputStream</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>
        <span class="n">people</span> <span class="o">=</span> <span class="o">(</span><span class="n">People</span><span class="o">)</span> <span class="n">inputStream</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">people</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>  <span class="c1">//虽然能得到对象，但是name属性并没有保存，因此为null
</span><span class="c1"></span>    <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="o">|</span> <span class="n">ClassNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="kd">static</span> <span class="kd">class</span> <span class="nc">People</span> <span class="kd">implements</span> <span class="n">Serializable</span><span class="o">{</span>
    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">serialVersionUID</span> <span class="o">=</span> <span class="n">1234567</span><span class="o">;</span>

    <span class="kd">transient</span> <span class="n">String</span> <span class="n">name</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">People</span><span class="o">(</span><span class="n">String</span> <span class="n">name</span><span class="o">){</span>
        <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div><p>其实我们可以看到，在一些JDK内部的源码中，也存在大量的transient关键字，使得某些属性不参与序列化，取消这些不必要保存的属性，可以节省数据空间占用以及减少序列化时间。</p>
<hr>
<h2 id="java-io编程实战">Java I/O编程实战</h2>
<h3 id="图书管理系统">图书管理系统</h3>
<p>要求实现一个图书管理系统（控制台），支持以下功能：保存书籍信息（要求持久化），查询、添加、删除、修改书籍信息。</p>
<blockquote>
<p>参考代码：</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">java.io.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.HashSet</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Objects</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Scanner</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Library</span> <span class="o">{</span>
    <span class="kd">static</span> <span class="kd">private</span> <span class="n">HashSet</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">MAP</span><span class="o">;</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">//TODO 注意先分配内存
</span><span class="c1"></span>        <span class="n">Scanner</span> <span class="n">scan</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Scanner</span><span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">in</span><span class="o">);</span>
        <span class="n">readData</span><span class="o">();</span>
        <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;============图书馆管理系统===========&#34;</span><span class="o">);</span>
            <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;1.插入数据&#34;</span><span class="o">);</span>
            <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;2.修改数据&#34;</span><span class="o">);</span>
            <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;3.查询图书列表&#34;</span><span class="o">);</span>
            <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;4.删除图书&#34;</span><span class="o">);</span>
            <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;(按下任意其他键退出程序)&#34;</span><span class="o">);</span>
            <span class="n">String</span> <span class="n">str</span> <span class="o">=</span> <span class="n">scan</span><span class="o">.</span><span class="na">nextLine</span><span class="o">();</span>
            <span class="k">switch</span> <span class="o">(</span><span class="n">str</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">case</span> <span class="s">&#34;1&#34;</span><span class="o">:</span>
                    <span class="n">insertBooks</span><span class="o">(</span><span class="n">scan</span><span class="o">);</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="k">case</span> <span class="s">&#34;2&#34;</span><span class="o">:</span>
                    <span class="n">modifyBook</span><span class="o">(</span><span class="n">scan</span><span class="o">);</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="k">case</span> <span class="s">&#34;3&#34;</span><span class="o">:</span>
                    <span class="n">showBooks</span><span class="o">();</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="k">case</span> <span class="s">&#34;4&#34;</span><span class="o">:</span>
                    <span class="n">deleteBooks</span><span class="o">(</span><span class="n">scan</span><span class="o">);</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="k">default</span><span class="o">:</span>
                    <span class="n">saveData</span><span class="o">();</span>
                    <span class="n">scan</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
                    <span class="k">return</span><span class="o">;</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>
    <span class="c1">//TODO 程序启动前，读取持久化数据
</span><span class="c1"></span>    <span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">readData</span><span class="o">(){</span>
        <span class="n">File</span> <span class="n">file</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="s">&#34;data&#34;</span><span class="o">);</span>
        <span class="k">if</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">exists</span><span class="o">()){</span>
            <span class="k">try</span><span class="o">(</span><span class="n">ObjectInputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ObjectInputStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">&#34;data&#34;</span><span class="o">))){</span>
                <span class="n">MAP</span> <span class="o">=</span> <span class="o">(</span><span class="n">HashSet</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;)</span> <span class="n">inputStream</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span>
            <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span><span class="o">|</span><span class="n">ClassNotFoundException</span> <span class="n">e</span><span class="o">){</span>
                <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
            <span class="o">}</span>
        <span class="o">}</span><span class="k">else</span> <span class="o">{</span>
            <span class="n">MAP</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HashSet</span><span class="o">&lt;&gt;();</span>
        <span class="o">}</span>
    <span class="o">}</span>
    <span class="c1">//TODO 程序结束时，持久序列化保存数据
</span><span class="c1"></span>    <span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">saveData</span><span class="o">(){</span>
        <span class="k">try</span><span class="o">(</span><span class="n">ObjectOutputStream</span> <span class="n">outputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ObjectOutputStream</span><span class="o">(</span><span class="k">new</span> <span class="n">FileOutputStream</span><span class="o">(</span><span class="s">&#34;data&#34;</span><span class="o">))){</span>
            <span class="n">outputStream</span><span class="o">.</span><span class="na">writeObject</span><span class="o">(</span><span class="n">MAP</span><span class="o">);</span>
            <span class="n">outputStream</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>
        <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">){</span>
            <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>
    <span class="c1">//TODO 修改图书数据
</span><span class="c1"></span>    <span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">modifyBook</span><span class="o">(</span><span class="n">Scanner</span> <span class="n">scan</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">showBooks</span><span class="o">();</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;输入您要修改书籍的索书号：&#34;</span><span class="o">);</span>
        <span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="n">scan</span><span class="o">.</span><span class="na">nextInt</span><span class="o">();</span>
        <span class="n">scan</span><span class="o">.</span><span class="na">nextLine</span><span class="o">();</span>
        <span class="n">Book</span> <span class="n">t</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Book</span><span class="o">().</span><span class="na">setIndex</span><span class="o">(</span><span class="n">index</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">MAP</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">t</span><span class="o">))</span> <span class="o">{</span>
            <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;输入错误图书馆内无该书籍!&#34;</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
            <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;请输入您要更改的信息：书名，作者，价格&#34;</span><span class="o">);</span>
            <span class="n">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">scan</span><span class="o">.</span><span class="na">nextLine</span><span class="o">();</span>
            <span class="n">String</span> <span class="n">author</span> <span class="o">=</span> <span class="n">scan</span><span class="o">.</span><span class="na">nextLine</span><span class="o">();</span>
            <span class="kt">double</span> <span class="n">price</span> <span class="o">=</span> <span class="n">scan</span><span class="o">.</span><span class="na">nextDouble</span><span class="o">();</span>
            <span class="n">scan</span><span class="o">.</span><span class="na">nextLine</span><span class="o">();</span>
            <span class="k">for</span> <span class="o">(</span><span class="n">Book</span> <span class="n">tt</span> <span class="o">:</span> <span class="n">MAP</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">tt</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">t</span><span class="o">))</span> <span class="o">{</span>
                    <span class="n">tt</span><span class="o">.</span><span class="na">setAuthor</span><span class="o">(</span><span class="n">author</span><span class="o">);</span>
                    <span class="n">tt</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="n">name</span><span class="o">);</span>
                    <span class="n">tt</span><span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="n">price</span><span class="o">);</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="o">}</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="c1">//TODO 删除书籍
</span><span class="c1"></span>    <span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">deleteBooks</span><span class="o">(</span><span class="n">Scanner</span> <span class="n">scan</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">showBooks</span><span class="o">();</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;请输入要删除书籍的索书号：&#34;</span><span class="o">);</span>
        <span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="n">scan</span><span class="o">.</span><span class="na">nextInt</span><span class="o">();</span>
        <span class="n">scan</span><span class="o">.</span><span class="na">nextLine</span><span class="o">();</span>
        <span class="n">Book</span> <span class="n">t</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Book</span><span class="o">().</span><span class="na">setIndex</span><span class="o">(</span><span class="n">index</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">MAP</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">t</span><span class="o">))</span> <span class="o">{</span>
            <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;索书号出错没有这样的书籍&#34;</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
            <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">printf</span><span class="o">(</span><span class="s">&#34;删除索书号为:%d 的书籍成功!\n&#34;</span><span class="o">,</span> <span class="n">index</span><span class="o">);</span>
            <span class="n">MAP</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">t</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="c1">//TODO 显示图书信息
</span><span class="c1"></span>    <span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">showBooks</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">MAP</span><span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="c1">//TODO 插入书籍
</span><span class="c1"></span>    <span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">insertBooks</span><span class="o">(</span><span class="n">Scanner</span> <span class="n">scan</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;请输入 索书号 书名 作者 价格。每输入一个信息按下回车键确认！&#34;</span><span class="o">);</span>
        <span class="n">MAP</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="n">Book</span><span class="o">().</span><span class="na">setIndex</span><span class="o">(</span><span class="n">scan</span><span class="o">.</span><span class="na">nextInt</span><span class="o">())</span>
                <span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="n">scan</span><span class="o">.</span><span class="na">nextLine</span><span class="o">()</span> <span class="o">+</span> <span class="n">scan</span><span class="o">.</span><span class="na">nextLine</span><span class="o">())</span>
                <span class="o">.</span><span class="na">setAuthor</span><span class="o">(</span><span class="n">scan</span><span class="o">.</span><span class="na">nextLine</span><span class="o">())</span>
                <span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="n">scan</span><span class="o">.</span><span class="na">nextDouble</span><span class="o">())</span>
        <span class="o">);</span>
        <span class="n">scan</span><span class="o">.</span><span class="na">nextLine</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="c1">//TODO 书籍类
</span><span class="c1"></span>    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">Book</span> <span class="kd">implements</span> <span class="n">Serializable</span><span class="o">{</span>
        <span class="kt">int</span> <span class="n">index</span><span class="o">;</span><span class="c1">//索书号
</span><span class="c1"></span>        <span class="n">String</span> <span class="n">name</span><span class="o">;</span><span class="c1">//书名
</span><span class="c1"></span>        <span class="n">String</span> <span class="n">author</span><span class="o">;</span><span class="c1">//作者
</span><span class="c1"></span>        <span class="kt">double</span> <span class="n">price</span><span class="o">;</span><span class="c1">//价格
</span><span class="c1"></span>
        <span class="kd">public</span> <span class="n">Book</span> <span class="nf">setName</span><span class="o">(</span><span class="n">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
            <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="kd">public</span> <span class="n">Book</span> <span class="nf">setIndex</span><span class="o">(</span><span class="kt">int</span> <span class="n">index</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">index</span> <span class="o">=</span> <span class="n">index</span><span class="o">;</span>
            <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="kd">public</span> <span class="n">Book</span> <span class="nf">setAuthor</span><span class="o">(</span><span class="n">String</span> <span class="n">author</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">author</span> <span class="o">=</span> <span class="n">author</span><span class="o">;</span>
            <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="kd">public</span> <span class="n">Book</span> <span class="nf">setPrice</span><span class="o">(</span><span class="kt">double</span> <span class="n">price</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">price</span> <span class="o">=</span> <span class="n">price</span><span class="o">;</span>
            <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="n">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
            <span class="k">return</span> <span class="s">&#34;书籍{&#34;</span> <span class="o">+</span>
                    <span class="s">&#34;索书号=&#34;</span> <span class="o">+</span> <span class="n">index</span> <span class="o">+</span>
                    <span class="s">&#34;, 书名=&#39;&#34;</span> <span class="o">+</span> <span class="n">name</span> <span class="o">+</span> <span class="sc">&#39;\&#39;&#39;</span> <span class="o">+</span>
                    <span class="s">&#34;, 作者=&#39;&#34;</span> <span class="o">+</span> <span class="n">author</span> <span class="o">+</span> <span class="sc">&#39;\&#39;&#39;</span> <span class="o">+</span>
                    <span class="s">&#34;, 价格=&#34;</span> <span class="o">+</span> <span class="n">price</span> <span class="o">+</span>
                    <span class="sc">&#39;}&#39;</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">equals</span><span class="o">(</span><span class="n">Object</span> <span class="n">o</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">if</span> <span class="o">(</span><span class="k">this</span> <span class="o">==</span> <span class="n">o</span><span class="o">)</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">o</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">getClass</span><span class="o">()</span> <span class="o">!=</span> <span class="n">o</span><span class="o">.</span><span class="na">getClass</span><span class="o">())</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
            <span class="n">Book</span> <span class="n">book</span> <span class="o">=</span> <span class="o">(</span><span class="n">Book</span><span class="o">)</span> <span class="n">o</span><span class="o">;</span>
            <span class="k">return</span> <span class="n">index</span> <span class="o">==</span> <span class="n">book</span><span class="o">.</span><span class="na">index</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="kt">int</span> <span class="nf">hashCode</span><span class="o">()</span> <span class="o">{</span>
            <span class="k">return</span> <span class="n">Objects</span><span class="o">.</span><span class="na">hash</span><span class="o">(</span><span class="n">index</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>

</code></pre></div></div><div class="post-footer" id="post-footer">
    <div class="post-info"><div class="post-info-tag"><span><a href="/tags/java-i/o/">Java I/O</a>
                </span></div><div class="post-info-line"><div class="post-info-mod">
                <span>更新于 2022-01-23</span>
            </div><div class="post-info-mod"></div>
        </div><div class="post-info-share">
            <span><a href="javascript:void(0);" title="分享到 Twitter" data-sharer="twitter" data-url="https://acking-you.github.io/posts/java-io/" data-title="Java I/O" data-hashtags="Java I/O"><i class="fab fa-twitter fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Facebook" data-sharer="facebook" data-url="https://acking-you.github.io/posts/java-io/" data-hashtag="Java I/O"><i class="fab fa-facebook-square fa-fw"></i></a><a href="javascript:void(0);" title="分享到 WhatsApp" data-sharer="whatsapp" data-url="https://acking-you.github.io/posts/java-io/" data-title="Java I/O" data-web><i class="fab fa-whatsapp fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Line" data-sharer="line" data-url="https://acking-you.github.io/posts/java-io/" data-title="Java I/O"><i class="fab fa-line fa-fw"></i></a><a href="javascript:void(0);" title="分享到 微博" data-sharer="weibo" data-url="https://acking-you.github.io/posts/java-io/" data-title="Java I/O" data-image="https://img-blog.csdnimg.cn/img_convert/e706274b97f65b3aac9471d1da024918.png#pic_center"><i class="fab fa-weibo fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Myspace" data-sharer="myspace" data-url="https://acking-you.github.io/posts/java-io/" data-title="Java I/O" data-description="Java I/O"><i data-svg-src="/lib/simple-icons/icons/myspace.min.svg"></i></a><a href="javascript:void(0);" title="分享到 Blogger" data-sharer="blogger" data-url="https://acking-you.github.io/posts/java-io/" data-title="Java I/O" data-description="Java I/O"><i class="fab fa-blogger fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Evernote" data-sharer="evernote" data-url="https://acking-you.github.io/posts/java-io/" data-title="Java I/O"><i class="fab fa-evernote fa-fw"></i></a></span>
        </div></div><div class="post-nav"><a href="/posts/java%E5%8F%8D%E5%B0%84%E4%B8%8E%E6%B3%A8%E5%B0%84/" class="prev" rel="prev" title="Java反射与注解"><i class="fas fa-angle-left fa-fw"></i>Previous Post</a>
            <a href="/posts/c&#43;&#43;%E4%B8%8Epython%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%AF%B9%E6%AF%94/" class="next" rel="next" title="C&#43;&#43;与python文件系统对比">Next Post<i class="fas fa-angle-right fa-fw"></i></a></div></div>
</div></article></div>
            </main>
            <footer class="footer"><div class="footer-container"><div class="footer-line">由 <a href="https://gohugo.io/" target="_blank" rel="noopener noreffer" title="Hugo 0.86.0">Hugo</a> 强力驱动 | 主题 - <a href="https://github.com/khusika/FeelIt" target="_blank" rel="noopener noreffer" title="FeelIt 1.0.1"><i class="fas fa-hand-holding-heart fa-fw"></i> FeelIt</a>
        </div><div class="footer-line" itemscope itemtype="http://schema.org/CreativeWork"><i class="far fa-copyright fa-fw"></i><span itemprop="copyrightYear">2023</span><span class="author" itemprop="copyrightHolder">&nbsp;<a href="/"></a></span></div>
</div>
</footer>
        </div>

        <div id="fixed-buttons"><a href="#" id="back-to-top" class="fixed-button" title="回到顶部">
                <i class="fas fa-chevron-up fa-fw"></i>
            </a></div><link rel="stylesheet" href="/lib/fontawesome-free/all.min.css"><link rel="stylesheet" href="/lib/animate/animate.min.css"><link rel="stylesheet" href="/lib/katex/katex.min.css"><link rel="stylesheet" href="/lib/katex/copy-tex.min.css"><script src="/lib/autocomplete/autocomplete.min.js"></script><script src="/lib/lunr/lunr.min.js"></script><script src="/lib/lunr/lunr.stemmer.support.min.js"></script><script src="/lib/lunr/lunr.zh.min.js"></script><script src="/lib/lazysizes/lazysizes.min.js"></script><script src="/lib/clipboard/clipboard.min.js"></script><script src="/lib/sharer/sharer.min.js"></script><script src="/lib/katex/katex.min.js"></script><script src="/lib/katex/auto-render.min.js"></script><script src="/lib/katex/copy-tex.min.js"></script><script src="/lib/katex/mhchem.min.js"></script><script>window.config={"code":{"copyTitle":"复制到剪贴板","maxShownLines":200},"comment":{},"math":{"delimiters":[{"display":true,"left":"$$","right":"$$"},{"display":true,"left":"\\[","right":"\\]"},{"display":false,"left":"$","right":"$"},{"display":false,"left":"\\(","right":"\\)"}],"strict":false},"search":{"highlightTag":"em","lunrIndexURL":"/index.json","lunrLanguageCode":"zh","lunrSegmentitURL":"/lib/lunr/lunr.segmentit.js","maxResultLength":100,"noResultsFound":"没有找到结果","snippetLength":50,"type":"lunr"}};</script><script src="/js/theme.min.js"></script></body></html>
